/**
 * title: DeviceDAO.java
 * description: Program that provides abstract interface to the database with the
 *              help of the Device model, which encapsulates device related items.
 * date: December 26 2013
 * id: 2540434
 * course: COMP 495 (Project-Server)
 * @author Mohamed B Thaha
 * @version 1.0
 * @copyright 2013 Mohamed B Thaha
 */

/**
 * DOCUMENTATION...
 */

/**                                                                               
 *
 *<H1>DeviceDAO</H1>
 *
 *<H3>Purpose and Description</H3>
 *
 *<P>
 * An application that allows the job of the DAO (data access object) to provide 
 * an abstract interface to the database.
 *</P>
 *
 *<P>
 * The DAOs main purpose is to perform CRUD operations on the database, which basically 
 * means create, retrieve, update, and delete data by virtue of a database connection or 
 * JDBC API driver.
 *</P>
 *
 *<P>
 * This program requires the Sun Java SDK version 1.7 or better.
 *</P>
 *                                                                              
 *<DL>
 *<DT> Compiling and running instructions</DT>
 *<DT> Assuming SDK 1.7 (or later) and the CLASSPATH are set up properly.</DT>
 *<DT> Assuming the use of Eclipse IDE (Kepler) 4.3.1.</DT>
 *<DT> Change to the directory containing the source code.</DT>
 *<DD> Compile:    DeviceDAO.java</DD>
 *<DD> Run:        DeviceDAO.java</DD>
 *<DD> Document:   Generate Javadoc DeviceDAO</DD>
 *</DL>
 */

/**
 *
 * <H3>Classes</H3>
 *
 *<P>
 * public class DeviceDAO {<BR>
 * This is the main public class for this application. It contains all the
 * methods required to perform CRUD operations.
 *</P>
 *
 * <H3>DeviceDAO Methods</H3>
 *
 *<P>
   public static boolean create(Device newFeature, long userID) throws Exception {<BR>
   This method returns either a true value indicating that the device has been
   registered into the database or a false value indicating that the insertion
   has failed.
 *</P>
 * 
 *<P>
   public static Device[] select(Hashtable<String, Object> device_filters) throws Exception {<BR>
   This method returns an array of Devices if such devices exist. The method also coincides
   with the FeatureConcept select method which retrieves an array of features as it relates
   to that particular device.
 *</P>
 *
 *<P>
   public static boolean update(Device device, Hashtable<String, Object> args) throws Exception {<BR>
   This method returns either a true value indicating that the update was a success 
   or a false value indicating that the update has failed.
 *</P> 
 *
 *<P>
   public static boolean delete(long deviceID) throws Exception {<BR>
   This method returns either a true value indicating that the device 
   and all device related data has been deleted or a false value indicating 
   that the deletion has failed.
 *</P> 
 * 
 * 
 * <H3>DeviceDAO Instance Variables (Tags - Constants)</H3>
 *
 *<P>
 * public static final String DEVICE_ID = "device_id"
 * public static final String ADD_FEATURE_NAME = "add_feature"
 * public static final String REMOVE_FEATURE_NAME = "remove_feature"
 * public static final String ADD_GROUPED_FEATURES = "add_grouped_features"
 *</P>
 */

/**
 * CODE...
 */

package athabasca.ca.dao;
/** Java core packages */
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.Hashtable;

import athabasca.ca.model.Device;
import athabasca.ca.model.FeatureConcept;
import athabasca.ca.util.*;
/** primary (public) class for DeviceDAO */
public class DeviceDAO {
	public static final String DEVICE_ID = "device_id";
	public static final String ADD_FEATURE_NAME = "add_feature";
	public static final String REMOVE_FEATURE_NAME = "remove_feature";
	public static final String ADD_GROUPED_FEATURES = "add_grouped_features";
	
	/**
	 *  Method's purpose is to insert a new device into the database
	 * @param deviceID
	 * device identification number
	 * @param userID
	 * user identification number
	 * @return
	 * boolean
	 * @throws Exception
	 */
	public static boolean create(Device newDevice, long userID) throws Exception {
		Connection conn = null;
		PreparedStatement query = null;
		boolean isTrue = false;
		
		FeatureConcept[] features = newDevice.getAvailableFeatures();
		
		try {
			// A 'safeguard' that checks if there is indeed at least one feature name present.
			if (userID <= 0) {
				return false;
			}
			else {
				conn = postgreSQL.postgreSQLConn();
				
				boolean insertFeature = false;
			
				// This query will only insert data into the device table.
				query = conn.prepareStatement("INSERT INTO device " + 
						"VALUES (?, ?)");
				query.setObject(1, newDevice.getDeviceId());
				query.setObject(2, userID);
				int insertDevice = query.executeUpdate();
				query.close();
				
				if (insertDevice == 1) {
				for (int i = 0; i < features.length; i++) {
					insertFeature = FeatureConceptDAO.create(newDevice.getDeviceId(), features[i]);
					if (insertFeature == false) {
						break;
					}
				}
				}
			
				// We must make sure that data was inserted in the device
				// table.
				if ((insertDevice == 1) && (insertFeature == true)) {
					isTrue = true;
				}
			}
			
		} catch (ArrayIndexOutOfBoundsException e) {
        	System.err.println ( e.getMessage( ) );
            e.printStackTrace ( );
            isTrue = false;
        } catch (NullPointerException e) {
        	System.err.println ( e.getMessage( ) );
        	e.printStackTrace ( );
        	isTrue = false;
        } catch (SQLException sqlError) {
        	System.err.println ( sqlError.getMessage( ) );
        	sqlError.printStackTrace ( );
        	isTrue = false;
        } catch (Exception e) {
        	System.err.println ( e.getMessage( ) );
        	e.printStackTrace();
        	isTrue = false;
        } finally {
        	if (conn != null) conn.close();
        }
		
		return isTrue;
	}
	
	/**
	 *  Method's purpose is to retrieve an array of selected Devices along with selected 
	 *  FeatureConcept array
	 * @param device_filters
	 * hash table
	 * @return
	 * Device[]
	 * @throws Exception
	 */
	public static Device[] select(Hashtable<String, Object> device_filters) throws Exception {
		PreparedStatement query = null;
		Connection conn = null;
		
		ArrayList<Device> devices = new ArrayList<Device>();
		
		try {
			// If the filters contained in the hash table are empty, return a null value 
			// and do not proceed to execute the rest of the code.
			if (device_filters.isEmpty())
				return null;
			else {
			
			String sql = "";
			String key = "";
			String statement = "";
			String statement2 = "";
			
			Enumeration<String> keys = device_filters.keys( );
			
			Hashtable<String, Object> filters;
			
			Device setDevice;
			
			// Build the 'statement' on the fly.
			while( keys.hasMoreElements( ) ) {
				key = keys.nextElement( );
				
				statement2 = "device.";
				
				if (key != DEVICE_ID)
					statement = statement + statement2 + key + " = '" + device_filters.get(key) + "'";
				else
					statement = statement + statement2 + key + " = " + device_filters.get(key);
				
				if (keys.hasMoreElements())
					statement = statement + " AND ";
			}
			
			sql = ("SELECT *" +
					" FROM device" +
					" WHERE " + statement);
			
			conn = postgreSQL.postgreSQLConn();
			
			// This query selects all data from the device table.
			query = conn.prepareStatement(sql);
			
			ResultSet results = query.executeQuery();
			
			// If the results returned from the database was empty, return a null value 
			// and there is no reason to continue to execute the rest of the code.
			if (results.wasNull())
				return null;
			else {
			
			while (results.next()) {
				filters = new Hashtable<String, Object>();
				setDevice = new Device();
				setDevice.setDeviceId(results.getInt(DEVICE_ID));
				
				filters.put(DEVICE_ID, results.getInt(DEVICE_ID));
				FeatureConcept[] features = FeatureConceptDAO.select(filters);
				
				setDevice.addAvailableFeatures(features);
				devices.add(setDevice);
				filters.clear();
			}
			}
			
			query.close();
			}
		} catch (ArrayIndexOutOfBoundsException e) {
        	System.err.println ( e.getMessage( ) );
            e.printStackTrace ( );     
        } catch (NullPointerException e) {
        	System.err.println ( e.getMessage( ) );
        	e.printStackTrace ( );
        } catch (SQLException sqlError) {
        	System.err.println ( sqlError.getMessage( ) );
        	sqlError.printStackTrace ( );
        } catch (Exception e) {
        	System.err.println ( e.getMessage( ) );
        	e.printStackTrace();
        } finally {
        	if (conn != null) conn.close();
        }
		
		Device[] deviceList = (Device[]) devices.toArray(new Device[devices.size()]);
		
		if (deviceList.length > 0)
	    	return deviceList;
	    else
	    	return null;
	}
	
	/**
	 *  Method's purpose is to update device related information based on the provided hash 
	 *  table filters
	 * @param id
	 * device identification number
	 * @param args
	 * hash table
	 * @return
	 * Boolean
	 * @throws Exception
	 */
	public static boolean update(Device device, Hashtable<String, Object> args) throws Exception {
		Connection conn = null;
        boolean isTrue = false;
            
        try {
        	// If the filters contained in the hash table are empty, return a null value 
        	// and do not proceed to execute the rest of the code.
        	if ((args.isEmpty()) || (device.getDeviceId() <= 0))
        		return false;
        	else {
        	
        	String key;
        	key = null;
        	
        	conn = postgreSQL.postgreSQLConn();
        	
        	Enumeration<String> keys = args.keys( );
            
        	// Build the 'statement' on the fly.
            while (keys.hasMoreElements()) {
            	key = keys.nextElement();

            	switch (key) {
            		case ADD_FEATURE_NAME:
            		{
                    	device.addFeature((FeatureConcept) args.get(ADD_FEATURE_NAME));
                    	
                    	isTrue = true;
                    	break;
            		}
            		case REMOVE_FEATURE_NAME:
            		{
                    	boolean update = device.removedFeature((FeatureConcept) args.get(REMOVE_FEATURE_NAME));
                    	if (update == true)
                    		isTrue = true;
                    	break;
            		}
            		case ADD_GROUPED_FEATURES:
            		{
                    	device.addAvailableFeatures((FeatureConcept[]) args.get(ADD_GROUPED_FEATURES));
               
                    	isTrue = true;
                    	break;
            		}
            		default:
            			isTrue = false;
            			return isTrue;
            	}
            }
        	}
        	
        } catch (ArrayIndexOutOfBoundsException e) {
        	System.err.println ( e.getMessage( ) );
            e.printStackTrace ( );     
            isTrue = false;
        } catch (NullPointerException e) {
        	System.err.println ( e.getMessage( ) );
        	e.printStackTrace ( );
        	isTrue = false;
        } catch (SQLException sqlError) {
        	System.err.println ( sqlError.getMessage( ) );
        	sqlError.printStackTrace ( );
        	isTrue = false;
        } catch (Exception e) {
        	System.err.println ( e.getMessage( ) );
        	e.printStackTrace();
        	isTrue = false;
        } finally {
        	if (conn != null) conn.close();
        }
        return isTrue;
    }
	
	/**
	 *  Method's purpose is to delete all devices and any device related 
	 *  data that equal the provided device id
	 * @param deviceID
	 * device identification number
	 * @return
	 * boolean
	 * @throws Exception
	 */
	public static boolean delete(long deviceID) throws Exception {
		Connection conn = null;
		PreparedStatement query = null;
        boolean isTrue = false;
        
        try {
        	if (deviceID <= 0)
        		return false;
        	else {
        	conn = postgreSQL.postgreSQLConn();
            	
        	query = conn.prepareStatement("DELETE FROM device"
        			+ " WHERE device_id = ?");
            query.setObject(1, deviceID);
        	int deleteDevice = query.executeUpdate();
            query.close();
            
            // We must make sure that data was deleted from the device
    		// table and its related data.
            if (deleteDevice > 0)
            	isTrue = true;
        	}
        	
        } catch (ArrayIndexOutOfBoundsException e) {
        	System.err.println ( e.getMessage( ) );
            e.printStackTrace ( );   
            isTrue = false;
        } catch (NullPointerException e) {
        	System.err.println ( e.getMessage( ) );
        	e.printStackTrace ( );
        	isTrue = false;
        } catch (SQLException sqlError) {
        	System.err.println ( sqlError.getMessage( ) );
        	sqlError.printStackTrace ( );
        	isTrue = false;
        } catch (Exception e) {
        	System.err.println ( e.getMessage( ) );
        	e.printStackTrace();
        	isTrue = false;
        } finally {
        	if (conn != null) conn.close();
        }
        return isTrue;
	}
} // End of public class DeviceDAO